home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1993 July / InfoMagic USENET CD-ROM July 1993.ISO / sources / misc / volume8 / nsort < prev    next >
Encoding:
Text File  |  1989-10-07  |  8.8 KB  |  325 lines

  1. Newsgroups: comp.sources.misc
  2. keywords: sort
  3. subject: v08i092: fast & simple sorting program
  4. From: allbery@uunet.UU.NET (Brandon S. Allbery - comp.sources.misc)
  5. Reply-To: ok@cs.mu.oz.au.UUCP (Richard O'Keefe)
  6.  
  7. Posting-number: Volume 8, Issue 92
  8. Submitted-by: ok@cs.mu.oz.au.UUCP (Richard O'Keefe)
  9. Archive-name: nsort
  10.  
  11. #!/bin/sh
  12. # This is a shell archive, meaning:                              
  13. # 1. Remove everything above the #!/bin/sh line.                
  14. # 2. Save the resulting text in a file.                          
  15. # 3. Execute the file with /bin/sh (not csh) to create the files:
  16. #    README
  17. #    makefile
  18. #    nsort.1
  19. #    nsort.c
  20. cat >README <<'------ EOF ------'
  21. nsort is a totally stripped down sort(1); it reads a file into memory,
  22. sorts it using merge sort, and writes the result out.  I wrote it to
  23. show that merge sort is fast -- sort(1) is documented as using a
  24. version of quicksort internally, and nsort beats it easily even when
  25. sort(1) is given enough memory to sort in memory.
  26. To compile it, just run make(1).
  27. ------ EOF ------
  28. ls -l README
  29. cat >makefile <<'------ EOF ------'
  30. nsort:
  31.     cc -o nsort -O nsort.c
  32. ------ EOF ------
  33. ls -l makefile
  34. cat >nsort.1 <<'------ EOF ------'
  35. .TH NSORT 1
  36. .\"/*%nroff -man %
  37. .SH NAME
  38. nsort \*- fast and simple sorting program
  39. .SH SYNOPSIS
  40. .B nsort
  41. <in >out
  42. .SH DESCRIPTION
  43. The
  44. .B nsort
  45. command
  46. reads lines from its standard input, sorts them, and writes the
  47. sorted lines to its standard output.  It behaves just like
  48. sort(1) when the latter is given no arguments.  The only reason
  49. for using
  50. .B nsort
  51. is speed:  when it can be used it is up to twice as fast as sort(1).
  52. .PP
  53. This program does
  54. .I not
  55. accept file names as arguments.  To process more than
  56. one input file, pipe the output of cat(1) into
  57. .B nsort.
  58. .PP
  59. The program does not need to know how many lines there
  60. are in the standard input, nor does it need to read the
  61. standard input more than once.  However, the data must
  62. fit into memory.
  63. .SH OPTIONS
  64. The
  65. .B nsort
  66. command has no options.
  67. .SH EXAMPLES
  68. .IP % 3
  69. ls -f . | nsort
  70. .PP
  71. has the same effect as a plain `ls'.
  72. .SH DIAGNOSTICS
  73. An error message is printed on stderr and the program halts without
  74. producing any output if
  75. .PP
  76. there are any arguments on the command line (status EX_USAGE is
  77. returned in this case), or
  78. .PP
  79. the program is unable to obtain enough memory using malloc (if the
  80. file has N lines and C characters, roughly 8N+C bytes are needed)
  81. (status EX_SOFTWARE is returned in this case), or
  82. .PP
  83. something goes wrong while reading standard input
  84. (status EX_IOERR is returned in this case).
  85. .PP
  86. All error messages indicate the actual name the program was invoked by.
  87. If an error message is produced, the rest of the standard input is not
  88. read, so you are likely to get "Broken pipe" messages from upstream
  89. processes.
  90. .SH BUGS
  91. Input lines which contain NUL characters are quietly truncated.
  92. sort(1) cannot handle such lines either, but complains.
  93. .SH "SEE ALSO"
  94. sort(1)
  95. .SH AUTHOR
  96. Richard A. O'Keefe
  97. ------ EOF ------
  98. ls -l nsort.1
  99. cat >nsort.c <<'------ EOF ------'
  100. /*  File   : nsort.c
  101.     Author : Richard A. O'Keefe
  102.     Updated: 1988
  103.     Purpose: Fast sorting command for smallish files.
  104.  
  105.     This program has no copyright notice.  You can do anything you please
  106.     with it, except blame me if it doesn't work.
  107. */
  108.  
  109. #ifndef    lint
  110. static    char SCCSid[] = "%Z%%E% %M%    %I%";
  111. #endif/*lint*/
  112.  
  113. /*  nsort <input >output
  114.  
  115.     is a very simple sorting program which has been written to be as fast
  116.     as possible.  It is equivalent to sort with no arguments.  That is,
  117.     it sorts its standard input to its standard output, and compares
  118.     entire lines.
  119.  
  120.     It uses a natural merge, so if the input is already in order it takes
  121.     linear time, and it reads the entire file into memory, using a single
  122.     read() if the standard input is a regular file.
  123.  
  124.     Sorting random permutations of /usr/dict/words on a Sun-3/50:
  125.     sort(1) : 15 seconds  nsort :  8 seconds  (with SCSI disc)
  126.           17 seconds          11 seconds  (NFS over Ethernet)
  127. */
  128.  
  129. #include <stdio.h>
  130.  
  131. /*  The following values are taken from <sysexits.h>, but are copied here
  132.     in case you lack that BSD header file.
  133. */
  134. #define    EX_OK         0    /* nothing went wrong */
  135. #define    EX_USAGE    64    /* something wrong with the command line */
  136. #define    EX_SOFTWARE    70    /* internal error (here, memory ran out) */
  137. #define    EX_IOERR    71    /* transput error (here, read() trouble) */
  138.  
  139. #define    STDIN         0
  140.  
  141. extern    char *    malloc();
  142. extern    char *    strcpy();
  143. extern    int    strcmp();
  144. extern    int    strlen();
  145. extern    void    perror();
  146. extern    int    lseek();
  147. extern    int    read();
  148.  
  149. typedef struct item_rec *item_ptr;
  150.  
  151. struct item_rec
  152.     {    
  153.     item_ptr    next;
  154.     char*         data;
  155.     };
  156.  
  157. #define    IN_ORDER(x, y) (strcmp((x)->data, (y)->data) <= 0)
  158.  
  159.  
  160. item_ptr nat_sort(list)
  161.     item_ptr list;
  162.     {
  163.     item_ptr stack[30];
  164.     item_ptr *sp = stack;
  165.     int runs = 0;            /* total number of runs processed */
  166.     register item_ptr p, q, r;
  167.     struct item_rec header;
  168.     int k;
  169.  
  170.     while (p = list) {
  171.         /* pick up a run from the front of list, setting */
  172.         /* p = (pointer to beginning of run), list = (rest of list) */
  173.         if (q = p->next) {
  174.         list = q->next;
  175.         if (!IN_ORDER(p, q)) r = q, q = p, p = r;
  176.         p->next = q;
  177.         for (r = list; r && IN_ORDER(q, r); r = r->next)
  178.             q->next = r, q = r;
  179.         q->next = (item_ptr)0;
  180.         list = r;
  181.         } else {
  182.         list = (item_ptr)0;
  183.         }
  184.         runs++;
  185.         /* merge this run with 0 or more runs off the top of the stack */
  186.         for (k = runs; 1 &~ k; k >>= 1) {
  187.         q = *--sp;
  188.         r = &header;
  189.         while (q && p)
  190.             if (IN_ORDER(q, p)) {
  191.             r->next = q;
  192.             r = q, q = q->next;
  193.             } else {
  194.             r->next = p;
  195.             r = p, p = p->next;
  196.             }
  197.         r->next = q ? q : p;
  198.         p = header.next;
  199.         }
  200.         /* push the merged run onto the stack */
  201.         *sp++ = p;
  202.     }
  203.     if (sp == stack) return (item_ptr)0;
  204.     /* merge all the runs on the stack */
  205.     p = *--sp;
  206.     while (sp != stack) {
  207.         q = *--sp;
  208.         r = &header;
  209.         while (q && p)
  210.         if (IN_ORDER(q, p)) {
  211.             r->next = q;
  212.             r = q, q = q->next;
  213.         } else {
  214.             r->next = p;
  215.             r = p, p = p->next;
  216.         }
  217.         r->next = q ? q : p;
  218.         p = header.next;
  219.     }
  220.     return p;
  221.     }
  222.  
  223. item_ptr alloc_item(data)
  224.     char *data;
  225.     {
  226.     register item_ptr result;
  227.  
  228.     result = (item_ptr)malloc(sizeof *result + strlen(data) + 1);
  229.     if (result) {
  230.         result->next = (item_ptr)0;
  231.         result->data = strcpy((char*)result + sizeof *result, data);
  232.     }
  233.     return result;
  234.     }
  235.  
  236. /*  What we really want to do is to slurp the entire file in with one call
  237.     to read().  In order to do that, we need to know how much there is.  A
  238.     file's size can be determined either by calling fstat() or by using an
  239.     lseek() to the end.  The number you get from fstat() doesn't mean much
  240.     for pipes and terminals, so lseek() appears to be the better approach.
  241.     Note that even when stdin is connected to a file, part of it may have
  242.     been read already.  In that case, we should not include the part which
  243.     has been read.  So we do
  244.     i = lseek(STDIN, 0, 1)
  245.     to discover the current position (and simultaneously to find out if we
  246.     can use lseek() at all on this file descriptor).  One problem remains;
  247.     the size of the file may change while we are reading it.  In that case
  248.     we'll stop early if we have to, but if the file grows we'll miss stuff
  249.     added at the end.
  250. */
  251. main(argc, argv)
  252.     int argc;
  253.     char **argv;
  254.     {
  255.     struct item_rec header;
  256.     item_ptr p, q;
  257.     char *progname = argc >= 1 ? argv[0] : "nsort";
  258.     int i;
  259.  
  260.     if (argc != 1) {
  261.         fprintf(stderr, "Usage: %s <unordered >sorted\n", progname);
  262.         exit(EX_USAGE);
  263.     }
  264.     i = lseek(STDIN, 0, 1);    /* current position in stream */
  265.     if (i < 0) {        /* can't find out the size by seeking */
  266.         char buffer[1024];
  267.  
  268.         for (p = &header
  269.         ; fgets(buffer, sizeof buffer, stdin)
  270.         ; p->next = q, p = q) {
  271.         i = strlen(buffer);
  272.         if (i > 0 && buffer[i-1] == '\n') buffer[i-1] = '\0';
  273.         if (!(q = alloc_item(buffer))) {
  274.             fprintf(stderr, "%s: ran out of memory\n", progname);
  275.             exit(EX_SOFTWARE);
  276.         }
  277.     } else {
  278.         register char *b, *c;
  279.         register int n;
  280.  
  281.         n = lseek(STDIN, 0, 2) - i;    /* part of stdin may have been read */
  282.         (void) lseek(STDIN, i, 0);    /* go back to original position */
  283.         if (!(b = malloc(n+1))) {
  284.         fprintf(stderr, "%s: ran out of memory\n", progname);
  285.         exit(EX_SOFTWARE);
  286.         }
  287.         for (c = b; (i = n-(c-b)) > 0; c += i) {
  288.         i = read(STDIN, c, i);
  289.         if (i < 0) {
  290.             perror(progname);
  291.             exit(EX_IOERR);
  292.         } else 
  293.         if (i == 0) {
  294.             break;
  295.         }
  296.         }
  297.         /* it is possible that the file may have been truncated */
  298.         /* while we were reading it. */
  299.         n = c-b;
  300.         for (p = &header; n > 0; b = c, p->next = q, p = q) {
  301.         for (c = b; --n >= 0; c++)
  302.             if (*c == '\n') {
  303.             *c++ = '\0';
  304.             break;
  305.             }
  306.         if (n < 0) *c = '\0';
  307.         if (!(q = (item_ptr) malloc(sizeof *q))) {
  308.             fprintf(stderr, "nsort: ran out of memory\n");
  309.             exit(EX_SOFTWARE);
  310.         }
  311.         q->data = b;
  312.         }
  313.     }
  314.     p->next = (item_ptr)0;
  315.     p = header.next;
  316.     for (p = nat_sort(p); p; p = p->next)
  317.         puts(p->data);
  318.     exit(EX_OK);
  319.     }
  320.  
  321. ------ EOF ------
  322. ls -l nsort.c
  323. exit 0
  324.  
  325.